﻿// FILE           :orbLogic.js
// PROJECT        :SETCritters                                        
// PROGRAMMER     :Kenneth Jinks and Ryan Pink                                         
// FIRST VERSION  :November 26 2013                                                
// DESCRIPTION    :
//      This file contains all of the javascript functions to handle the client side game controller.
//      It uses websockets to communicate with the server environment on port 5001, and handles all
//      graphical components of the controller in a canvas. In addition, touch events are handled by
//      HTML5.

var cvs;        // The canvas
var ctx;        // The context of the canvas

var BaseX = 170;// joystick bounded width
var BaseY = 170;// joystick bounded height

var MouseX = 0; // mouse's current X position
var MouseY = 0; // mouse's current Y position

var MoveVecX = 0;   // The joystick's current vector X from centre
var MoveVecY = 0;   // The joystick's current vector Y from centre

var pressEvt = "no event...";   // Debug statement for touch events
var isMouseDown = false;        // Mousedown flag for handling touch events

var Colour = 0;                 // What colour did the user choose
var fromID = Math.floor((Math.random() * 10) + 1);

var PORT = 5001;    //this is the port to connect to the server
var serverIP = "";  //serverIP is given by the user from the input box named ipAddress
var websocket;      //websocket manages the connection to the server
var firstPacket = true;


//this is the raw codes that are sent in the packet, they all have a fixed 
//length of ten chars
PacketMessages =
{
    //            0123456789
    MSG_NEW: "NEW-=-=-=-",
    MSG_TX: "TRANSMIT-=",
    MSG_RX: "RECEIVE-=-",
    MSG_QUIT: "QUIT-=-=-=",
    MSG_ERR: "ERROR-=-=-",
    MSG_PULSE: "PULSE=-=-=",
    MSG_CATCH: "CATCH=-=-=",
    MSG_MOVE: "MOVE-=-=-="
};


//this is the enumerations for the commands sent by packet
CommandEnum =
{
    mNEW: 0,
    mTX: 1,
    mRX: 2,
    mQUIT: 3,
    mERROR: 4,
    mPULSE: 5,
    mCATCH: 6,
    mMOVE: 7
};


//message space struct
function MessageSpace() {
    this.clientTx = "";
    this.clientRx = "";
    this.lastAccessed = 0;
    this.writing = 0;
    this.assigned = 0;
}


//message received struct
function MessageReceived() {
    this.message = "";
    this.fromID = 0;
    this.toID = 0;
    this.cmd = CommandEnum.mERROR;
}

//============================================================================
//          CANVAS FUNCTIONS
//============================================================================

// LoadControls()
//   This function is called when the user taps Start. It prepares the
//   canvas and fullscreen's it in for the user.
function LoadControls() {

    Colour = document.getElementById("mycolour").value;

    connectToServer();      // connect via websocket

    var controls = "<canvas id='controller'></canvas>";
    document.getElementById("fullscreen").innerHTML = controls;

    cvs = document.getElementById("controller");

    ctx = cvs.getContext("2d");

    document.addEventListener("touchmove", function (e) { e.preventDefault(); }, false); // disable two-finger scrolling and zooming

    ToFullScreen(); // force a fullscreen

    cvs.width = window.innerWidth;
    cvs.height = window.innerHeight;
    ctx.canvas.width = window.innerWidth;
    ctx.canvas.height = window.innerHeight;

    cvs.addEventListener("touchmove", UpdateMouse, false);  // add touch handling events
    cvs.addEventListener("touchstart", UpdateMouse, false);
    cvs.addEventListener("touchend", ClearMouse, false);

    DrawCanvas();
    ctx.fillText("Press the RED button to see your Orb!", 400, 20);
}

//  DrawCanvas()
//    This function draws the graphical components to the canvas
function DrawCanvas() {
    var img = document.getElementById("joystickbase");
    ctx.drawImage(img, 0, 0);

    img = document.getElementById("joystick");
    ctx.drawImage(img, MoveVecX + BaseX - 75, MoveVecY + BaseY - 75);

    img = document.getElementById("pulsebtn");
    ctx.drawImage(img, 440, 40);

    img = document.getElementById("capturebtn");
    ctx.drawImage(img, 640, 40);

    img = document.getElementById("cagebg");
    ctx.drawImage(img, 460, 220);

    // Load the critter if it exists
    //          . . .
    // Right here...

    img = document.getElementById("cage");
    ctx.drawImage(img, 460, 220);

    ctx.fillStyle = "#FFFFFF";
    ctx.font = "18px Arial";
}


// ClearMouse()
//   This function resets the mouse values when the user lifts their finger and ends a touch event.
//   This effectively snaps the joystick back in place and clears the debug text.
function ClearMouse(e) {

    MoveVecX = 0;
    MoveVecY = 0;

    ctx.clearRect(0, 0, cvs.width, cvs.height);
    DrawCanvas();
}


// UpdateMouse()
//   This function is called for every touchstart and touchmove event handled by the canvas.
//   It updates the debug text (if any) and handles collision with the controller components
//   (i.e. joystick, buttons)
function UpdateMouse(e) {
    // this if-statement is a work-around to the NEW packet not being sent
    // when the connection is first established.
    if (firstPacket == true) {      // first touch, send a new user packet to the server
        var message = createMessage(Colour, fromID, 0, CommandEnum.mNEW);
        sendPacket(message);

        firstPacket = false;
    }

    MouseX = e.touches[0].pageX;
    MouseY = e.touches[0].pageY;

    pressEvt = "";

    // This is a JOYSTICK touch event
    if (0 < MouseX && MouseX < 383 && 0 < MouseY && MouseY < 382) {
        mX = MouseX - BaseX;
        mY = MouseY - BaseY;

        var mH = Math.sqrt(mX * mX + mY * mY);

        if (mH > 100) {     // Bind the joystick to the circle of the base
            var pH = 100;
            var pX = (mX * pH) / mH;
            var pY = (mY * pH) / mH;

            MoveVecX = pX;
            MoveVecY = pY;
        } else {            // grab the vector from the base's centre
            MoveVecX = mX;
            MoveVecY = mY;
        }

        var message = createMessage(MoveVecX.toFixed(4) + "," + MoveVecY.toFixed(4), fromID, 0, CommandEnum.mMOVE);
        sendPacket(message);    // Send the packet to the server.

        pressEvt = message;     // debug echo of the packet.
    } else {

        if (MoveVecX != 0 || MoveVecY != 0) {   // Reset the joystick position
            MoveVecX = 0;
            MoveVecY = 0;

            var message = createMessage(MoveVecX + "," + MoveVecY, fromID, 0, CommandEnum.mMOVE);
            sendPacket(message);    // send the packet to the server.
        }

        // This is a ORB PULSE BUTTON touch event
        if (MouseX > 440 && MouseX < 590 && MouseY > 40 && MouseY < 190) {
            var message = createMessage("", fromID, 0, CommandEnum.mPULSE);
            sendPacket(message);    // send the packet to the server.

            pressEvt = message;     // debug echo of the packet.
        }
        // This is a CRITTER CATCH touch event
        else if (MouseX > 640 && MouseX < 790 && MouseY > 40 && MouseY < 190) {
            var message = createMessage("", fromID, 0, CommandEnum.mCATCH);
            sendPacket(message);    // send the packet to the server.

            pressEvt = message;     // debug echo of the packet.
        }
        // This is a CRITTER CAGE touch event
        else if (MouseX > 460 && MouseX < 753 && MouseY > 220 && MouseY < 385) {
            pressEvt = "CRITTER CAGE PRESSED!";
        }
    }

    ctx.clearRect(0, 0, cvs.width, cvs.height);
    DrawCanvas();

    ctx.fillStyle = "#FFFFFF";
    //ctx.fillText(pressEvt, 400, 20);
}


// ToFullScreen()
//   http://www.sitepoint.com/html5-full-screen-api/
//   This function attempts to set the browser status to fullscreen.
function ToFullScreen() {

    if (RunPrefixMethod(document, "FullScreen") || RunPrefixMethod(document, "IsFullScreen")) {
        RunPrefixMethod(document, "CancelFullScreen");
    }
    else {
        RunPrefixMethod(document.getElementById("fullscreen"), "RequestFullScreen");
    }
}


// RunPrefixMethod()
//   http://www.sitepoint.com/html5-full-screen-api/
//   This function, as scraped from the URL above, attempts to determine the host browser
//   and creates the proper call to fullscreen the browser.
function RunPrefixMethod(obj, method) {

    var pfx = ["webkit", "moz", "ms", "o", ""];
    var p = 0, m, t;
    while (p < pfx.length && !obj[m]) {
        m = method;
        if (pfx[p] == "") {
            m = m.substr(0, 1).toLowerCase() + m.substr(1);
        }
        m = pfx[p] + m;
        t = typeof obj[m];
        if (t != "undefined") {
            pfx = [pfx[p]];
            return (t == "function" ? obj[m]() : obj[m]);
        }
        p++;
    }
}


//============================================================================
//          WEBSOCKET FUNCTIONS
//============================================================================

/*
FUNCTION    : function createMessage(message, fromID, toID, cmd)
DESCRIPTION : This function constructs a packet that will later be sent to the 
server. The packet has fixed field lengths, fromID and toID get padded to be 
four chars wide and the cmd is translated to a 10 char wide field. This means the
first 18 chars of the packet tell where the packet is going, who it is from and
what the sender is commanding the receiver to do.
PARAMETERS  : 
INPUT       :message - the message data being sent 
            :fromID  - the senders ID number
            :toID    - the receivers ID number (server is 0)
            :cmd     - the command from sender to receiver
OUTPUT      :n/a
RETURNS     :a string containing the newly minted packet
*/
function createMessage(message, fromID, toID, cmd) {
    var commandString = "";

    switch (cmd) {
        case (CommandEnum.mNEW):
            commandString = PacketMessages.MSG_NEW;
            break;
        case (CommandEnum.mTX):
            commandString = PacketMessages.MSG_TX;
            break;
        case (CommandEnum.mRX):
            commandString = PacketMessages.MSG_RX;
            break;
        case (CommandEnum.mQUIT):
            commandString = PacketMessages.MSG_QUIT;
            break;
        case (CommandEnum.mPULSE):
            commandString = PacketMessages.MSG_PULSE;
            break;
        case (CommandEnum.mCATCH):
            commandString = PacketMessages.MSG_CATCH;
            break;
        case (CommandEnum.mMOVE):
            commandString = PacketMessages.MSG_MOVE;
            break;
        default:
            commandString = PacketMessages.MSG_ERR;
    }

    var fromIDString = "0000" + fromID;     //pad id's wth zeros
    fromIDString = fromIDString.substring(fromIDString.length - 4);

    var toIDString = "0000" + toID;
    toIDString = toIDString.substring(toIDString.length - 4);

    //construct packet
    var packet = fromIDString + commandString + toIDString + message;

    return packet;
}


/*
FUNCTION    :function readMessage(packetIn) 
DESCRIPTION :This function takes a packet string and pulls out the fromID, 
command, toID and message and populates a MessageReceived object that
it returns.
PARAMETERS  : 
INPUT       :packetIn - the packet string to be parsed
OUTPUT      :
RETURNS     :MessageReceived - this object contains the parsed information from
the packet string.
*/
function readMessage(packetIn) {
    var fromID = Number(packetIn.substring(1, 4));
    var commandString = packetIn.substring(4, 14);
    var toID = Number(packetIn.substring(15, 18));
    var cmd = CommandEnum.mERROR;

    if (commandString === PacketMessages.MSG_NEW) {
        cmd = CommandEnum.mNEW;
    }else if (commandString === PacketMessages.MSG_QUIT) {
        cmd = CommandEnum.mQUIT;
    }
    else if (commandString === PacketMessages.MSG_PULSE) {
        cmd = CommandEnum.mPULSE;
    }
    else if (commandString === PacketMessages.MSG_CATCH) {
        cmd = CommandEnum.mCATCH;
    }
    else {
        cmd = CommandEnum.mERROR;
    }

    var messageReceived = new MessageReceived();
    messageReceived.fromID = fromID;
    messageReceived.toID = toID;
    messageReceived.cmd = cmd;
    messageReceived.message = packetIn.substring(18);

    return messageReceived;
}

/*
FUNCTION    : function connectToServer()
DESCRIPTION : This function validates the IP address with Regex then a new 
instantiation of the websocket is generated.
PARAMETERS  : 
INPUT       :
OUTPUT      :
RETURNS     :
*/
function connectToServer() {
    //code modified from
    //http://www.websocket.org/echo.html
    serverIP = "127.0.0.1";

    //regex to validate ip address
    var validIP = /[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/;

    if (validIP.test(serverIP)) {
        //alert("Connecting to " + serverIP + ":" + PORT + "...");
        websocket = new WebSocket("ws://" + serverIP + ":" + PORT);
        websocket.onopen = function (evt) { onOpen(evt); };
        websocket.onclose = function (evt) { onClose(evt); };
        websocket.onmessage = function (evt) { onMessage(evt); };
        websocket.onerror = function (evt) { onError(evt); };
    }
    else {
        alert("Invalid IP address (" + serverIP + ")");
    }
}

/*
FUNCTION    :function onOpen(evt) 
DESCRIPTION :This function is the event handler for the websocket onOpen event 
PARAMETERS  : 
INPUT       :evt - parameter passed from websocket call
OUTPUT      :
RETURNS     :
*/
function onOpen(evt) {
    //code modified from
    //http://www.websocket.org/echo.html
    //writeToOutputBox("<b>Connected</b>");

    //jinks code below
    //if the user pushes the connect button while already 
    //connected to server the server will have an orphaned
    //client which takes up resources
    //document.getElementById("connectBtn").disabled = true;
}

/*
FUNCTION    :function onClose(evt) 
DESCRIPTION :This function is the event handler for the websocket onClose event 
PARAMETERS  : 
INPUT       :evt - parameter passed from websocket call
OUTPUT      :
RETURNS     :
*/
function onClose(evt) {
    //code modified from
    //http://www.websocket.org/echo.html
    //writeToOutputBox("<b>Disconnected</b>");

    //jinks code below
    //connection closes reenable the connect button so the 
    //user can reconnect
    //document.getElementById("connectBtn").disabled = false;
}

/*
FUNCTION    :function closeAll() 
DESCRIPTION :This function is called during the unload event, it is intended to 
close the open connection and send the server the command to end session.
PARAMETERS  : 
INPUT       :
OUTPUT      :
RETURNS     :
*/
function closeAll() {
    //send message to server to end session
    //sendPacket(createMessage("", 9999, 9999));
}

/*
FUNCTION    :function onMessage(evt) 
DESCRIPTION :This function is the event handler for the websocket onMessage event 
PARAMETERS  : 
INPUT       :evt - parameter passed from websocket call
OUTPUT      :
RETURNS     :
*/
function onMessage(evt) {
    //code modified from
    //http://www.websocket.org/echo.html
    //writeToOutputBox("Incoming:<br>" + evt.data);
}

/*
FUNCTION    :function onError(evt)
DESCRIPTION :This function is the event handler for the websocket onError event 
PARAMETERS  : 
INPUT       :evt - parameter passed from websocket call
OUTPUT      :
RETURNS     :
*/
function onError(evt) {
    //code modified from
    //http://www.websocket.org/echo.html   
    writeToOutputBox("Error:<br>" + evt.data);
}


function writeToOutputBox(message) {
    alert(message);
}


/*
FUNCTION    :function sendPacket(message)
DESCRIPTION :This function sends a message to the server. 
PARAMETERS  : 
INPUT       :message - message to send to server
OUTPUT      :
RETURNS     :
*/
function sendPacket(message) {
    //value of the websocket ready state when open
    var WEBSOCKET_OPEN = 1;

    //while (websocket.readyState === 0) { }

    try {
        //check if websocket is available first
        if (typeof (websocket) === "undefined") {
            alert("Please connect first");
        }
        else if (websocket.readyState === WEBSOCKET_OPEN) {
            websocket.send(message);
        }
        else {
            alert("Error socket not open. " + websocket.readyState);
        }
    }
    catch (error) {
        alert("Error packet not sent (" + error + ")");
    }

}